﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace FAST
{

    public enum GUIFileStatus
    {
        FILE_NOT_EXIST = 7, //copy file over
        FILE_MODIFIED,
        FILE_IDENTICAL
    }

    /// <summary>
    /// The actual comparison of Original State of left file details list and right file details list.
    /// </summary>
    public class FileSystem
    {
        private List<FilesHandler> fileDetailsLeft;
        private List<FilesHandler> fileDetailsRight;
        private string[] folderLTempList;
        private string[] folderRTempList;

        private List<FilesHandler> osLeftFileDetails;
        private List<FilesHandler> osRightFileDetails;

        //GUI status rules
        /*private const int FILE_NOT_EXIST = 7; //copy file over
        private const int FILE_MODIFIED = 8;
        private const int FILE_IDENTICAL = 9;*/



        /// <summary>
        /// Contructor that initializes a List<FilesHandler> of both left and right folders. Sets List<FilesHandler> of OriginalState to default null. Initializes 2 List of strings which stores file path names with its content modified. These 2 data structure serves to propagate dirty bit changes made to a file to its root folder.
        /// </summary>
        public FileSystem()
        {
            fileDetailsLeft = new List<FilesHandler>();
            fileDetailsRight = new List<FilesHandler>();
            osLeftFileDetails = null;
            osRightFileDetails = null;

            folderLTempList = new string[0];//store files with content modified and propagate the dirty bit to its parent folder
            folderRTempList = new string[0];//store files with content modified and propagate the dirty bit to its parent folder
        }


        /// <summary>
        /// Sets List<FilesHandler> of OriginalState to a previously defined List<FilesHandler>, ie the original state of a folder kept as persistent data.
        /// </summary>
        /// <param name="osLeftList">The original state of left list</param>
        /// <param name="osRightList">The original state of right list</param>
         public void setOSFileDetails(List<FilesHandler> osLeftList, List<FilesHandler> osRightList)
        {
            osLeftFileDetails = osLeftList;
            osRightFileDetails = osRightList;
        }

         /// <summary>
         /// If List<FilesHandler> of left OriginalState is null, recursively traverse through a folder to return its List<FilesHandler>. 
         /// Else recursively traverse through a folder and at the same time compares the file with all files in the OriginalState, returning a List<FilesHandler> at the end.
         /// </summary>
         /// <param name="path">The child folder path</param>
         /// <param name="rootPath">The path of root directory</param>
        public void generateLeftFolderTree(string path, string rootPath)
        {
            string subPath = "";
            string[] leftFileList = Directory.GetFiles(path);
            string[] leftFolderList = Directory.GetDirectories(path);
            if (osLeftFileDetails == null) //OS dont exist (generate hash code for all files)
            {
                
                try
                {     
                    Array.Sort(leftFileList);
                    foreach (string file in leftFileList)
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));
                        fileDetailsLeft.Add(fileDetail);
                      
                    }
      
                    Array.Sort(leftFolderList);
                    foreach (string folder in leftFolderList)
                    {
                        FolderHandler folderDetail = new FolderHandler(folder, folder.Substring(rootPath.Length));
                        folderDetail.setDirtyBit(true);//always set folder dirty bit true for first sync
                        
                        fileDetailsLeft.Add(folderDetail);

                        generateLeftFolderTree(folder, rootPath);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
            else //OS exist
            {
                
                Array.Sort(leftFileList);
                foreach (string file in leftFileList)
                {
                    FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                    fileDetail.setMd5HashCode(computeHashCode(file));

                    subPath = file.Substring(rootPath.Length);
                    int status = compareFolderToOriginal(rootPath, subPath, osLeftFileDetails, fileDetail);
                    //Note: recon need to transfer modified and newly added FileHandler from new leftORright folder list to OS list
                    //this is to ensure all FileHandler in OS has md5HashCode at all times

                    if (fileDetail.isDirty())
                    {
                        subPath = subPath.Substring(0, subPath.LastIndexOf('\\'));
                        if (!subPath.Equals(""))
                        {
                            string[] tokens = subPath.Split('\\');
                            if (folderLTempList.Length < tokens.Length)
                                folderLTempList = tokens;
                        }
                    }

                    fileDetail.setFileStatus(status);
                    fileDetailsLeft.Add(fileDetail);
                }

                
                Array.Sort(leftFolderList);
                foreach (string folder in leftFolderList)
                {
                    subPath = folder.Substring(rootPath.Length);
                    FolderHandler folderDetail = new FolderHandler(folder, subPath);
                    int status2 = compareFolderToOriginal(rootPath, subPath, osLeftFileDetails, folderDetail);
                    fileDetailsLeft.Add(folderDetail);
                    int index = fileDetailsLeft.Count - 1;


                    generateLeftFolderTree(folder, rootPath);

                    if (folderLTempList.Length > 0)
                    {

                        string[] tokens2 = subPath.Split('\\');
                        int size = folderLTempList.Length - 1;

                        //to propagate same content dirty bit to root folders
                        if (folderLTempList[size].Equals(tokens2[tokens2.Length - 1]))
                        {
                            ((FolderHandler)fileDetailsLeft[index]).setDirtyBit(true);
                            
                            //Remove last element from array
                            string[] tempArr = new string[size];
                            for (int i = 0; i < size; i++)
                            {
                                tempArr[i] = folderLTempList[i];
                            }
                            folderLTempList = tempArr;
                            //End Remove
                        }

                    }
                }
            }

        }


        /// <summary>
        /// If List<FilesHandler> of right OriginalState is null, recursively traverse through a folder and at the same compares each file with all files from the List<FilesHandler> obtain from generateLeftFolderTree to return a List<FilesHandler>. Else recursively traverse through a folder and at the same time compares the file with all files in the OriginalState, returning a  List<FilesHandler> at the end.
        /// </summary>
        /// <param name="path">The child folder path</param>
        /// <param name="rootPath">The path of root directory</param>
        public void generateRightFolderTree(string path, string rootPath)
        {
            string subPath = "";
            string[] rightFileList = Directory.GetFiles(path);
            string[] rightFolderList = Directory.GetDirectories(path);
            try
            {          
                if (osRightFileDetails == null)//OS DONT EXIST
                {

                    Array.Sort(rightFileList);
                    foreach (string file in rightFileList)
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));
                        int status = compareFileRightToLeftFiles(rootPath, file.Substring(rootPath.Length), fileDetail);
                        fileDetail.setFileStatus(status);
                        fileDetailsRight.Add(fileDetail);

                    }


                    Array.Sort(rightFolderList);
                    foreach (string folder in rightFolderList)
                    {
                        FolderHandler folderDetail = new FolderHandler(folder, folder.Substring(rootPath.Length));

                        int status = compareFileRightToLeftFiles(folder, folder.Substring(rootPath.Length), folderDetail);
                        folderDetail.setFolderStatus(status);
                        folderDetail.setDirtyBit(true);//always set folder dirty bit true for first sync

                        fileDetailsRight.Add(folderDetail);

                        generateRightFolderTree(folder, rootPath);
                    }
                    /*
                    if (fileDetailsLeft.Count > fileDetailsRight.Count)
                    {
                        int start = fileDetailsRight.Count;
                        int size = fileDetailsLeft.Count;
                        for (int i = start; i < size; i++)
                        {
                            if (!fileDetailsLeft[i].getFileType())
                            {
                                ((FolderHandler)fileDetailsLeft[i]).setDirtyBit(true);
                            }
                        }
                    }
                    */
                }
                else//OS EXIST
                {
                   
                    Array.Sort(rightFileList);
                    foreach (string file in rightFileList)
                    {
                        FileHandler fileDetail = new FileHandler(file, file.Substring(rootPath.Length));

                        fileDetail.setMd5HashCode(computeHashCode(file));

                        subPath = file.Substring(rootPath.Length);
                        int status = compareFolderToOriginal(rootPath, subPath, osRightFileDetails, fileDetail);

                        if (fileDetail.isDirty())
                        {
                            subPath = subPath.Substring(0, subPath.LastIndexOf('\\'));
                            if (!subPath.Equals(""))
                            {
                                string[] tokens = subPath.Split('\\');
                                if (folderRTempList.Length < tokens.Length)
                                    folderRTempList = tokens;
                            }
                        }

                        fileDetail.setFileStatus(status);
                        fileDetailsRight.Add(fileDetail);
                    }

                   
                    Array.Sort(rightFolderList);
                    foreach (string folder in rightFolderList)
                    {
                        subPath = folder.Substring(rootPath.Length);
                        FolderHandler folderDetail = new FolderHandler(folder, subPath);
                        int status2 = compareFolderToOriginal(rootPath, subPath, osRightFileDetails, folderDetail);
                        fileDetailsRight.Add(folderDetail);
                        int index = fileDetailsRight.Count - 1;

                        generateRightFolderTree(folder, rootPath);

                        if (folderRTempList.Length > 0) //virtual folder dirty list 
                        {
                            string[] tokens2 = subPath.Split('\\');
                            int size = folderRTempList.Length - 1;

                            //to propagate same content to root folders
                            if (folderRTempList[size].Equals(tokens2[tokens2.Length - 1]))
                            {
                                ((FolderHandler)fileDetailsRight[index]).setDirtyBit(true);

                                //Remove last element from array
                                string[] tempArr = new string[size];
                                for (int i = 0; i < size; i++)
                                {
                                    tempArr[i] = folderRTempList[i];
                                }
                                folderRTempList = tempArr;
                                //End Remove
                            }

                        }
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// This method basically accepts a file and compare it with all files from an original state.
        /// </summary>
        /// <param name="fullPath">The full path of directory</param>
        /// <param name="subPath">The path of the file</param>
        /// <param name="osFileDetails">Original State of File Details</param>
        /// <param name="fileDetail">File Details in the FilesHandler</param>
        /// <returns>Returns report of file, the report includes FILE_NOT_EXIST, FILE_MODIFIED and FILE_IDENTICAL </returns>
        /// <exception cref="DirectoryNotFoundException">The specified path is invalid</exception>
        public int compareFolderToOriginal(string fullPath, string subPath, List<FilesHandler> osFileDetails, FilesHandler fileDetail/*can be left or right file or folder*/)
        {
            bool folderDirtyBit = true;
            int REPORT = (int)GUIFileStatus.FILE_NOT_EXIST;
            if (osFileDetails != null)
            {
                foreach (FilesHandler fh in osFileDetails)//os file details
                {
                    if (fh.isTypeFile() && fileDetail.isTypeFile()) //check if its a file, true = file
                    {
                        if (subPath.Equals(((FileHandler)fh).getSubFilePath()))//check file name and location exist in both folders
                        {
                            ((FileHandler)fileDetail).setVersioningIdx(((FileHandler)fh).getVersioningIdx());
                            ((FileHandler)fileDetail).setIsInOS(true);

                            //check file size and file timestamp
                            if ((((FileHandler)fileDetail).getFileLength() == ((FileHandler)fh).getFileLength()) && (((FileHandler)fileDetail).getLastModTime() == ((FileHandler)fh).getLastModTime()))
                            {
                                //perform md5 check
                                if (((FileHandler)fileDetail).getMd5HashCode().Equals(((FileHandler)fh).getMd5HashCode()))
                                {
                                    ((FileHandler)fileDetail).setDirtyBit(false);
                                    REPORT = (int)GUIFileStatus.FILE_IDENTICAL;
                                }
                                else
                                {  
                                    //Conflict
                                    REPORT = (int)GUIFileStatus.FILE_MODIFIED;
                                    ((FileHandler)fileDetail).setIsTimeConflict(true);
                                }
                            }
                            else
                            {
                                REPORT = (int)GUIFileStatus.FILE_MODIFIED;
                                //if current file timestamp less than OS -> raise conflict
                                if ((((FileHandler)fileDetail).getLastModTime() < ((FileHandler)fh).getLastModTime()))
                                    ((FileHandler)fileDetail).setIsTimeConflict(true);
                            }
                            break;
                        }

                    }

                    else if (!(fh.isTypeFile() || fileDetail.isTypeFile()))//if folder (De Morgan's law !(p OR q) == !p AND !q )
                    {
                        if (subPath.Equals(((FolderHandler)fh).getSubFolderPath()))//check file name and location exist in both folders
                        {
                            folderDirtyBit = false;
                            break;
                        }
                    }
                }

                if (!fileDetail.isTypeFile())
                    ((FolderHandler)fileDetail).setDirtyBit(folderDirtyBit);
            }
            else
            {
                MessageBox.Show("Original State do not exist!"); 
            }
            return REPORT;
        }


        /// <summary>
        /// This method basically accepts a file and compare it with all files from the source (left) folder.
        /// </summary>
        /// <param name="fullPath">The full path of directory</param>
        /// <param name="subPath">The path of the file</param>
        /// <param name="rightfileDetail">Right File Details in the FilesHandler</param>
        /// <returns>Returns report of file, the report includes FILE_NOT_EXIST, FILE_MODIFIED and FILE_IDENTICAL</returns>
        /// <exception cref="DirectoryNotFoundException">The specified path is invalid</exception>
        public int compareFileRightToLeftFiles(string fullPath, string subPath, FilesHandler rightfileDetail)
        {
            //bool isFolderExist = true;
            int REPORT = (int)GUIFileStatus.FILE_NOT_EXIST;
            int index = 0;
            foreach (FilesHandler fh in fileDetailsLeft)//left fileDetails
            {
                if (fh.isTypeFile() && rightfileDetail.isTypeFile()) //check if its a file, true = file
                {

                    if (subPath.Equals(((FileHandler)fh).getSubFilePath()))//check file name and location exist in both folders
                    {
                        //check file size and file timestamp
                        if ((((FileHandler)rightfileDetail).getFileLength() == ((FileHandler)fh).getFileLength()) && (((FileHandler)rightfileDetail).getLastModTime() == ((FileHandler)fh).getLastModTime()))
                        {
                            //perform md5 check
                            if (((FileHandler)rightfileDetail).getMd5HashCode().Equals(((FileHandler)fh).getMd5HashCode()))
                            {
                                REPORT = (int)GUIFileStatus.FILE_IDENTICAL;
                                ((FileHandler)rightfileDetail).setDirtyBit(false);
                                ((FileHandler)fileDetailsLeft[index]).setDirtyBit(false);
                            }
                            else
                            {
                                REPORT = (int)GUIFileStatus.FILE_MODIFIED;
                            }
                        }
                        else
                        {
                            REPORT = (int)GUIFileStatus.FILE_MODIFIED;
                            if ((((FileHandler)rightfileDetail).getLastModTime() > ((FileHandler)fh).getLastModTime()))
                                //rightfileDetail.setDirtyBit(true);
                                ((FileHandler)fileDetailsLeft[index]).setDirtyBit(false);
                            else
                                //((FileHandler)fileDetailsLeft[index]).setDirtyBit(true);
                                ((FileHandler)rightfileDetail).setDirtyBit(false);

                        }
                        ((FileHandler)fileDetailsLeft[index]).setFileStatus(REPORT);//set file status in left folder list
                        break;
                    }

                }
                
                else if (!(fh.isTypeFile() || rightfileDetail.isTypeFile()))//if folder (De Morgan's law !(p OR q) == !p AND !q )
                {
                    if (subPath.Equals(((FolderHandler)fh).getSubFolderPath()))//check file name and location exist in both folders
                    {
                        REPORT = (int)GUIFileStatus.FILE_IDENTICAL; //FOR USE IN FOLDER COMPARE
                        ((FolderHandler)fileDetailsLeft[index]).setFolderStatus(REPORT);//set folder status in left folder list

                        //isFolderExist = false;
                        //((FolderHandler)fileDetailsLeft[index]).setDirtyBit(false);
                        break;
                    }
                }
                
                index++;
            }
            
            return REPORT;
        }


        /// <summary>
        /// Returns a List<FilesHandler> obtain from generateLeftFolderTree
        /// </summary>
        /// <returns>Return details of left files</returns>
        public List<FilesHandler> getLeftFileDetails()
        {
            return fileDetailsLeft;
        }

        //Method name : getRightFileDetails() : List<FilesHandler>
        //Parameters  : None.
        //Description : returns a List<FilesHandler> obtain from generateRightFolderTree
        //Exceptions  : None.
        public List<FilesHandler> getRightFileDetails()
        {
            return fileDetailsRight;
        }

        //execute this method after checking modified time and size
        //*******************************************************************************//

        /// <summary>
        /// This method computes the md5 hash code of a file given its full file path.
        /// </summary>
        /// <param name="f">The full file path</param>
        /// <returns>Return string of string builder</returns>
        /// <exception cref="UnauthorizedAccessException">If the user is not allowed to access the drive.</exception>
        public string computeHashCode(string f)
        {
            StringBuilder sb = new StringBuilder();
            try
            {
                System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();

                using (FileStream fs = File.Open(f, FileMode.Open))
                {
                    foreach (byte b in md5.ComputeHash(fs))
                        sb.Append(b.ToString("x2").ToLower());
                }
            }
            catch
            {
            }
            return sb.ToString();
        }


    }
}
